/*	daemon		1.11.2011
 *
 * a daemon progam providing frames for kmnl - the camera simulating device
 *
 * based on code from Tomer Margalit's netlink example
 * 
 * read the README file for further explanations
 */

#include <stdio.h>
#include <unistd.h>

#include <libavformat/avformat.h>
#include <libswscale/swscale.h>

#include <SDL/SDL.h>
#include <SDL/SDL_thread.h>

#include <netlink/netlink.h>
#include <netlink/genl/genl.h>
#include <netlink/genl/ctrl.h>
#include <netlink/socket.h>

#include "../common/common.h"
#include "../camdev/camdev.h"

//constants
#define DAEMON_QUIT			-1
#define DAEMON_SEND_FRAME		1

#define ATTR_MAX 3

#define ERR_ON(exp, msg...)  \
do { if (exp) { printf(msg); return 0; } } while (0)

#define ERR_ON_IN(exp, msg...)  \
do { if (exp) { printf(msg); return 0; } } while (0)



#ifdef __MINGW32__
#undef main /* Prevents SDL from overriding main() */
#endif



//some global variables
char *msg_buffer;
char *msg_buffer_ptr;
int frame_size;
char frameReady;
char *ctxBuff;
int ctxBuffSize = 0;
int OP = 0;


//the function Nezer provided
void img_convert(AVPicture * target , int targetFmt, AVPicture * source ,int sourceFmt,int w, int h)
{
	static struct SwsContext *img_convert_ctx=NULL;
	if(img_convert_ctx == NULL)
	{
		img_convert_ctx = sws_getContext(w, h,
				sourceFmt, w, h, targetFmt, SWS_BICUBIC,NULL, NULL, NULL);
	}
	sws_scale(img_convert_ctx, source->data,
			source->linesize, 0,
			h, target->data, target->linesize);
}

struct nla_policy policies[MAX_ATTR_INDEX + 1] = {
		[FRAME_ATTR] = {.type = NLA_UNSPEC },
		[FIN_ATTR] = {.type = NLA_UNSPEC },
		[CTX_ATTR] = {.type = NLA_UNSPEC },
		[EO_FRAME_ATTR] = {.type = NLA_UNSPEC },
		[REQ_FRAME_ATTR] = {.type = NLA_UNSPEC },
		[OK_ATTR] = {.type = NLA_UNSPEC },
		[START_ATTR] = {.type = NLA_UNSPEC },
		[QUIT_ATTR] = {.type = NLA_UNSPEC }
};


int sendCtx(struct nl_handle *sock, int family, struct nl_msg *msg);


static int parse_cb(struct nl_msg *msg, void *arg)
{
	struct nlmsghdr *nlh = nlmsg_hdr(msg);
	struct nlattr *attrs[MAX_ATTR_INDEX + 1];
	
	// Validate message and parse attributes
	genlmsg_parse(nlh, 0, attrs, MAX_ATTR_INDEX + 1, policies);


	struct nlattr *fin_attr = attrs[FIN_ATTR];
	struct nlattr *req_frame_attrs = attrs[REQ_FRAME_ATTR];
	struct nlattr *ok_attr = attrs[OK_ATTR];
	struct nlattr *quit_attr = attrs[QUIT_ATTR];

	printf("in parse_cb.\n");
	if (req_frame_attrs){

		return NL_STOP;
	}
	else if (ok_attr){
		printf("got an okay from the kernel\n");
		return NL_STOP;
	}
	else if (fin_attr){
		printf("got an okay on last frame\n");
		return NL_STOP;
	}
	else if (quit_attr){
		printf("got a signal to exit\n");
		OP = -1;
		return NL_STOP;
	}
	return NL_OK;
}

int send_first_msg(struct nl_handle *sock, int family, struct nl_msg *msg){
	int vrv, rv;

	printf("started sending first message\n");fflush(stdout);

	msg = nlmsg_alloc();
	if(msg == NULL){
		printf("Error allocating message.\n");
		return 0;
	}

	printf("genlmsg_put\n");fflush(stdout);
	vrv = genlmsg_put(msg, NL_AUTO_PID, NL_AUTO_SEQ, family, 0, NLM_F_ECHO, 1, 1);
	if (vrv == NULL){
		printf("Error - genlmsg returned NULL!\n");
		nlmsg_free(msg);
		return 0;
	}

	rv = nla_put(msg, START_ATTR, strlen("start"), "start");
	if (rv < 0){
		printf("Error - nla_put returned negative result!\n");
		nlmsg_free(msg);
		return 0;
	}

	// Send message over netlink socket
	rv = nl_send_auto_complete(sock, msg);
	if (rv < 0){
		printf("Error sending the message (%d).\n", rv);
		nlmsg_free(msg);
		return 0;
	}


	if (nl_recvmsgs_default(sock)){ printf("failed once again\n");}
	nl_wait_for_ack(sock);
	nlmsg_free(msg);
	printf("finished sending signal\n");fflush(stdout);

	return 1;
}

int sendCtx(struct nl_handle *sock, int family, struct nl_msg *msg){
	int vrv, rv;

	printf("started sending ctx\n");

	msg = nlmsg_alloc();
	if(msg == NULL){
		printf("Error allocating message.\n");
		return 0;
	}

	vrv = genlmsg_put(msg, NL_AUTO_PID, NL_AUTO_SEQ, family, 0, NLM_F_ECHO, 1, 1);
	if (vrv == NULL){
		printf("Error - genlmsg returned NULL!\n");
		nlmsg_free(msg);
		return 0;
	}

	rv = nla_put(msg, CTX_ATTR, ctxBuffSize, ctxBuff);
	if (rv < 0){
		printf("Error - nla_put returned negative result!\n");
		nlmsg_free(msg);
		return 0;
	}

	// Send message over netlink socket
	rv = nl_send_auto_complete(sock, msg);
	if (rv < 0){
		printf("Error sending the message (%d).\n", rv);
		nlmsg_free(msg);
		return 0;
	}

	if (nl_recvmsgs_default(sock)){ printf("failed once again\n");}
	nl_wait_for_ack(sock);
	nlmsg_free(msg);
	printf("finished sending ctx\n");fflush(stdout);

	return 1;
}



int send_frame_nl(struct nl_handle *sock, int family, struct nl_msg *msg, void *frameBuffer, int buffSize){
	int vrv, rv, msgLen = 0;
	char *ptr = frameBuffer;
	int attrnum;

	printf("started send_frame_nl\n");fflush(stdout);
	printf("Adding a frame to the message.\n");fflush(stdout);
	while (buffSize > 0) {

		//nlmsg_set_default_size(MSG_BUFFER_SIZE);
		msg = nlmsg_alloc();
		if(msg == NULL){
			printf("Error allocating message.\n");
			return 0;
		}

		vrv = genlmsg_put(msg, NL_AUTO_PID, NL_AUTO_SEQ, family, 0, NLM_F_ECHO, 1, 1);
		if (vrv == NULL){
			printf("Error - genlmsg returned NULL!\n");
			nlmsg_free(msg);
			return 0;
		}

		//put a chunk from the buffer to msg
		if (buffSize > MSG_BUFFER_SIZE){
			msgLen = MSG_BUFFER_SIZE;
			attrnum = FRAME_ATTR;
		}
		else {
			msgLen = buffSize;
			attrnum = EO_FRAME_ATTR;
			printf("sending last chunk of the frame, buffSoze: %d\n", buffSize);
		}
		rv = nla_put(msg, attrnum, msgLen, ptr);
		if (rv < 0){
			printf("Error - nla_put returned negative result!\n");
		}
		ptr += msgLen;
		buffSize -= msgLen;

		// Send message over netlink socket
		printf("sending chunk.\n");fflush(stdout);
		rv = nl_send_auto_complete(sock, msg);
		if (rv < 0){
			printf("Error sending the message (%d).\n", rv);
		}

		if (nl_recvmsgs_default(sock)){ printf("failed once again\n");}
		nl_wait_for_ack(sock);
		nlmsg_free(msg);
	}
	printf("exiting send_nl_frame\n");fflush(stdout);
	return 1;
}


int send_fin_nl(struct nl_handle *sock, int family, struct nl_msg *msg){
	int vrv, rv;

	printf("started send_fin\n");fflush(stdout);

	msg = nlmsg_alloc();
	if(msg == NULL){
		printf("Error allocating message.\n");
		return 0;
	}

	vrv = genlmsg_put(msg, NL_AUTO_PID, NL_AUTO_SEQ, family, 0, NLM_F_ECHO, 1, 1);
	if (vrv == NULL){
		printf("Error - genlmsg returned NULL!\n");
		nlmsg_free(msg);
		return 0;
	}

	rv = nla_put(msg, FIN_ATTR, strlen("fin"), "fin");
	if (rv < 0){
		printf("Error - nla_put returned negative result!\n");
	}
	rv = nl_send_auto_complete(sock, msg);
	if (rv < 0){
		printf("Error sending the message (%d).\n", rv);
	}

	if (nl_recvmsgs_default(sock)){ printf("failed once again\n");}
	nl_wait_for_ack(sock);
	nlmsg_free(msg);
	printf("exiting send_fin\n");fflush(stdout);
	return 1;
}


void *ctx2bin(int width, int height, int format, int *retSize){
	int *buffer;
	int *ptr;

	buffer = (int *)malloc((sizeof(int)*3));
	if (buffer == NULL) {
		*retSize = 0;
		return NULL;
	}
	memset(buffer, 0, (sizeof(int)*3));

	*retSize = sizeof(int)*3;

	ptr = buffer;
	memcpy(ptr, &width, sizeof(int));

	ptr++;
	memcpy(ptr, &height, sizeof(int));

	ptr++;
	memcpy(ptr, &format, sizeof(int));

	return buffer;
}

int frame2bin(AVFrame *frame, int height){

	char *buffer = msg_buffer, *ptr = NULL;


	ptr = buffer;

	memcpy(ptr, frame->data[0], frame->linesize[0]*height);

	printf("exiting frame2bin\n");fflush(stdout);
	return 1;
}

int main(int argc, char *argv[]) {

	AVFormatContext *pFormatCtx;
	int             i, videoStream;
	AVCodecContext  *pCodecCtx;
	AVCodec         *pCodec;
	AVFrame         *pFrame; 
	AVPacket        packet;
	int             frameFinished;
	int             numBytes;
	uint8_t         *buffer;
	AVPicture 	pict;

	struct nl_handle *sock;
	struct nl_msg *msg;
	int family;
	int rv = 0;
	int v;

	if(argc < 2) {
		printf("Please provide a movie file\n");
		return -1;
	}


	if (daemon(1,0) != 0){
		return -1;
	}

	// Register all formats and codecs -- CODEC
	av_register_all();


	if(SDL_Init(SDL_INIT_VIDEO | SDL_INIT_AUDIO | SDL_INIT_TIMER)) {
		fprintf(stderr, "Could not initialize SDL - %s\n", SDL_GetError());
		return -1;
	}
	printf("sdl_init\n");fflush(stdout);	//DEBUG

	// Open video file	--CODEC
	if(av_open_input_file(&pFormatCtx, argv[1], NULL, 0, NULL)!=0)
		return -1; // Couldn't open file
	printf("fav_open_input_file\n");fflush(stdout);	//DEBUG

	// Retrieve stream information
	if(av_find_stream_info(pFormatCtx)<0)
		return -1; // Couldn't find stream information
	printf("av_find_stream_info\n");fflush(stdout);	//DEBUG

	// Dump information about file onto standard error
	dump_format(pFormatCtx, 0, argv[1], 0);
	printf("dump_format\n");fflush(stdout);	//DEBUG

	// Find the first video stream
	videoStream=-1;
	for(i=0; i<(int)pFormatCtx->nb_streams; i++)
		if(pFormatCtx->streams[i]->codec->codec_type==CODEC_TYPE_VIDEO) {
			videoStream=i;
			break;
		}
	if(videoStream==-1)
		return -1; // Didn't find a video stream

	// Get a pointer to the codec context for the video stream
	pCodecCtx=pFormatCtx->streams[videoStream]->codec;

	// Find the decoder for the video stream
	pCodec=avcodec_find_decoder(pCodecCtx->codec_id);
	if(pCodec==NULL) {
		fprintf(stderr, "Unsupported codec!\n");
		return -1; // Codec not found
	}
	// Open codec
	if(avcodec_open(pCodecCtx, pCodec)<0)
		return -1; // Could not open codec

	// Allocate video frame
	pFrame=avcodec_alloc_frame();

	//save binary codecCtx info to global vars.


	ctxBuff = ctx2bin(pCodecCtx->width, pCodecCtx->height, PIX_FMT_RGB24, &ctxBuffSize);
	printf("width: %d\theight: %d\tformat: %d\n", pCodecCtx->width, pCodecCtx->height, pCodecCtx->pix_fmt);

	//set up things for the frame buffer
	frame_size = pCodecCtx->width*pCodecCtx->height*4;
	msg_buffer = (char *)malloc(frame_size);
	if (msg_buffer == NULL){
		return 0;
	}

	//reset the global buffer + ptr
	memset(msg_buffer, 0, frame_size);

	msg_buffer_ptr = msg_buffer;

	//do nl stuff
	printf("Allocating a socket.\n");

	// Allocate a new netlink socket
	sock = nl_handle_alloc();
	ERR_ON(sock == NULL, "Failed allocating socket.\n");
	printf("Socket allocated successfully.\n");

	printf("Connecting the socket...\n");

	//Disable sequence checks.
	nl_disable_sequence_check(sock);
	//Set the local port
	nl_socket_set_local_port(sock, 1000);

	// Connect to generic netlink socket on kernel side
	rv = genl_connect(sock);
	ERR_ON(rv, "Error connecting the socket (%d).\n", rv);
	printf("Socket connected successfully.\n");fflush(stdout);

	printf("Resolving family.\n");fflush(stdout);


	// Ask kernel to resolve family name to family id
	family = genl_ctrl_resolve(sock, FAMILY_NAME);
	ERR_ON(family < 0, "Error resolving family (%d).\n", family);
	printf("The family was resolved successfully.\n");fflush(stdout);


	nl_socket_modify_cb(sock, NL_CB_VALID, NL_CB_CUSTOM, parse_cb, NULL);


	if (send_first_msg(sock, family, msg) != 1){
		printf("unable to talk to device\n");
	}

	if (sendCtx(sock, family, msg) != 1){
		printf("unable to talk to device\n");
	}


	printf("finished with NL\n");fflush(stdout);	//DEBUG

	//======== end of nl stuff!



	printf("before while\n");fflush(stdout);//DEBUG

	//	FINISHED INITIALIZING
	// Determine required buffer size and allocate buffer
	numBytes=avpicture_get_size(PIX_FMT_RGB24, pCodecCtx->width, pCodecCtx->height);
	printf("!!!!!!!!!!!!numbytes: %d\n", numBytes);
	buffer=(uint8_t *)av_malloc(numBytes*sizeof(uint8_t));


	avpicture_fill((AVPicture *)&pict, buffer, PIX_FMT_RGB24, pCodecCtx->width, pCodecCtx->height);
	printf("linesize[0]: %d\n", pict.linesize[0]);
	frame_size = numBytes;



	while((v = av_read_frame(pFormatCtx, &packet))>=0) {

		//check if we got a quit signal		
		if (OP == -1){
			av_free_packet(&packet);
			break;
		}

		// Is this a packet from the video stream?
		if(packet.stream_index==videoStream) {
			// Decode video frame
			avcodec_decode_video(pCodecCtx, pFrame, &frameFinished, packet.data, packet.size);

			// Did we get a video frame?
			if(frameFinished) {

				img_convert(&pict, PIX_FMT_RGB24, (AVPicture *)pFrame, pCodecCtx->pix_fmt, pCodecCtx->width, pCodecCtx->height);

				frame2bin((AVFrame *)&pict, pCodecCtx->height);

				send_frame_nl(sock, family, msg, msg_buffer, frame_size);

			}
		}
		av_free_packet(&packet);
	}


	send_fin_nl(sock, family, msg);

	printf("exiting daemon\n");
	//close up frame buffer resources
	free(ctxBuff);
	free(msg_buffer);

	// Free the YUV frame
	av_free(pFrame);

	// Close the codec
	avcodec_close(pCodecCtx);

	// Close the video file
	av_close_input_file(pFormatCtx);

	return 0;

}

